package br.ugf.projeto.agentes;

import br.ugf.projeto.util.MessageConstants;
import br.ugf.projeto.util.Produto;
import br.ugf.projeto.util.PropertiesHandler;
import br.ugf.projeto.util.Proposta;
import jade.core.AID;
import jade.core.Agent;
import jade.core.behaviours.Behaviour;
import jade.core.behaviours.TickerBehaviour;
import jade.domain.DFService;
import jade.domain.FIPAException;
import jade.domain.FIPAAgentManagement.DFAgentDescription;
import jade.domain.FIPAAgentManagement.ServiceDescription;
import jade.lang.acl.ACLMessage;
import jade.lang.acl.MessageTemplate;

import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;

/**
 * <p>
 * </p>
 * 
 * @author Rodrigo Souza, Alexandre Zulu, Fabio Girotto
 * @version 1.0 Created on 01/06/2008
 */
public abstract class Consumidor
	extends Agent
{

	/**
	 * <p>
	 * </p>
	 * 
	 * @author Rodrigo Souza, Alexandre Zulu, Fabio Girotto
	 * @version 1.0 Created on 30/03/2008
	 */
	protected class IniciaCFP
		extends Behaviour
	{

		/**
		 * <p>
		 * Field <code>serialVersionUID</code>
		 * </p>
		 */
		private static final long serialVersionUID = 1L;

		/**
		 * <p>
		 * </p>
		 * 
		 * @param produto
		 * @param qtde
		 */
		public IniciaCFP( final String produto, final int qtde )
		{
			this.step = 0;
			this.repliesCnt = 0;
			this.produtoNome = produto;
			this.quantidadeProduto = qtde;
			this.fornecedores = procuraFornecedores( produto );
			System.out.printf( "%s INICIANDO CFP - %s, %d\n", getLocalName(), produto, qtde );
		}

		/**
		 * <p>
		 * </p>
		 * 
		 * @see jade.core.behaviours.Behaviour#action()
		 */
		@Override
		public void action()
		{
			try
			{
				switch ( this.step )
				{
					case 0 :
						// Send the cfp to all sellers
						ACLMessage cfp = new ACLMessage( ACLMessage.CFP );
						for ( int i = 0; i < this.fornecedores.length; ++i )
						{
							cfp.addReceiver( this.fornecedores[i] );
						}
						cfp.addUserDefinedParameter( MessageConstants.PARAMETER_PRODUTO, this.produtoNome );
						cfp.addUserDefinedParameter( MessageConstants.PARAMETER_QTDE, String
							.valueOf( this.quantidadeProduto ) );
						cfp.setContent( MessageConstants.CONTENT_PROPOSAL );
						cfp.setConversationId( MessageConstants.COVERSATION_ID );
						cfp.setReplyWith( "cfp" + System.currentTimeMillis() ); // Unique value
						this.myAgent.send( cfp );
						System.out.printf( "%s ENVIA CFP %s\n", getLocalName(), cfp );
						// Prepare the template to get proposals
						this.template = MessageTemplate.and( MessageTemplate
							.MatchConversationId( MessageConstants.COVERSATION_ID ), MessageTemplate
							.MatchInReplyTo( cfp.getReplyWith() ) );
						this.step = 1;
						break;
					case 1 :
						// Receive all proposals/refusals from seller agents and look for the lowest
						// price
						ACLMessage reply = this.myAgent.blockingReceive( this.template, 30000 );
						this.repliesCnt++;
						if ( this.repliesCnt >= this.fornecedores.length )
						{
							this.step = 2;// We received all replies
						}
						if ( reply != null )
						{
							// Reply received
							if ( reply.getPerformative() == ACLMessage.PROPOSE )
							{
								// This is an offer
								Proposta newOffer = new Proposta(
									reply.getUserDefinedParameter( MessageConstants.PARAMETER_PRODUTO ),
									reply.getSender(),
									Float.parseFloat( reply.getContent() ),
									Integer.parseInt( reply.getUserDefinedParameter( MessageConstants.PARAMETER_QTDE ) ) );
								this.listaPropostas.add( newOffer );
								System.out.printf( "" + "%s RECEBE PROPOSTA %s\n", getLocalName(), reply );
							}
						}
						break;
					case 2 :
						// Sort the seller agent list accordingly to their price offers
						Collections.sort( this.listaPropostas );
						try
						{
							Proposta propostaAceita = this.listaPropostas.get( 0 );
							ACLMessage order = new ACLMessage( ACLMessage.ACCEPT_PROPOSAL );
							order.setContent( MessageConstants.CONTENT_PROPOSAL );
							order.addReceiver( propostaAceita.getFornecedor() );
							order.addUserDefinedParameter( MessageConstants.PARAMETER_PRODUTO, propostaAceita
								.getProduto() );
							order.addUserDefinedParameter( MessageConstants.PARAMETER_VALOR, String
								.valueOf( propostaAceita.getValor() ) );
							order.addUserDefinedParameter( MessageConstants.PARAMETER_QTDE, String
								.valueOf( propostaAceita.getQuantidade() ) );
							order.setConversationId( MessageConstants.COVERSATION_ID );
							order.setReplyWith( MessageConstants.REPLY_WITH_ORDER );
							this.myAgent.send( order );
							this.template = MessageTemplate.and( MessageTemplate
								.MatchConversationId( MessageConstants.COVERSATION_ID ), MessageTemplate
								.MatchInReplyTo( order.getReplyWith() ) );
							System.out.printf( "%s ENVIA ACEPT_PROPOSAL %s\n", getLocalName(), order );
							this.step = 3;
						}
						catch ( RuntimeException e )
						{
							this.step = 0;
						}
						break;
					case 3 :
						// Receive the purchase order reply
						reply = this.myAgent.blockingReceive( this.template, 30000 );
						if ( reply != null )
						{
							// Purchase order reply received
							if ( reply.getPerformative() == ACLMessage.INFORM )
							{
								System.out.printf(
									"%s PRODUTO RECEBIDO %s - Quantidade %s\n",
									this.myAgent.getLocalName(),
									reply.getUserDefinedParameter( MessageConstants.PARAMETER_PRODUTO ),
									reply.getUserDefinedParameter( MessageConstants.PARAMETER_QTDE ) );
							}
						}
						this.step = 4;
						break;
				} // End of switch
			}
			catch ( Exception e )
			{
				e.printStackTrace();
			}
		}

		/**
		 * <p>
		 * </p>
		 * 
		 * @return
		 * @see jade.core.behaviours.Behaviour#done()
		 */
		@Override
		public boolean done()
		{
			return ( this.step == 4 );
		}

		/**
		 * <p>
		 * Field <code>fornecedores</code>
		 * </p>
		 */
		private AID[] fornecedores;

		/**
		 * <p>
		 * Field <code>listaPropostas</code>
		 * </p>
		 */
		private List<Proposta> listaPropostas = new LinkedList<Proposta>();

		/**
		 * <p>
		 * Field <code>produtoNome</code>
		 * </p>
		 */
		private String produtoNome;

		/**
		 * <p>
		 * Field <code>quantidadeProduto</code>
		 * </p>
		 */
		private int quantidadeProduto;

		/**
		 * <p>
		 * Field <code>repliesCnt</code>
		 * </p>
		 */
		private int repliesCnt;

		/**
		 * <p>
		 * Field <code>step</code>
		 * </p>
		 */
		private short step;

		/**
		 * <p>
		 * Field <code>template</code>
		 * </p>
		 */
		private MessageTemplate template;

	}

	/**
	 * <p>
	 * </p>
	 * 
	 * @param produto
	 * @param qtde
	 */
	private void baixaEstoque( final String produto, final int qtde )
	{
		int qtdeEstoque = this.estoque.get( produto );
		this.estoque.put( produto, ( qtdeEstoque - qtde ) );
	}

	/**
	 * <p>
	 * </p>
	 * 
	 * @param produto
	 * @param qtde
	 */
	public void compraRealizada( final String produto, final int qtde )
	{
		System.out.printf( "%s COMPRA REALIZADA - Produto %s, Quantidade %d\n", getAID().getLocalName(), produto, qtde );
		verificaEstoque( produto, qtde );
	}

	/**
	 * <p>
	 * </p>
	 * 
	 * @param tipo
	 * @return
	 */
	protected AID[] procuraFornecedores( final String tipo )
	{
		// Update the list of seller agents
		System.out.println( getAID().getLocalName() + " buscando fornecedores do tipo: " + tipo );
		AID[] fornecedores = null;
		DFAgentDescription template = new DFAgentDescription();
		ServiceDescription sd = new ServiceDescription();
		sd.setType( tipo );
		template.addServices( sd );
		try
		{
			DFAgentDescription[] result;
			result = DFService.search( this, template );
			fornecedores = new AID[result.length];
			for ( int i = 0; i < result.length; ++i )
			{
				fornecedores[i] = result[i].getName();
			}
			System.out.printf(
				"%s encontrou %d agentes fornecedores de %s\n",
				getAID().getLocalName(),
				result.length,
				tipo );
		}
		catch ( FIPAException fe )
		{
			System.err.println( getAID().getName() + " FALHOU AO BUSCAR " + tipo );
			fe.printStackTrace();
		}
		return fornecedores;
	}

	/**
	 * <p>
	 * </p>
	 * 
	 * @see jade.core.Agent#setup()
	 */
	@Override
	protected void setup()
	{
		System.out
			.println( "--------------------------------------------------------------------------------------------------" );
		System.out.printf( "%s - Agente iniciado  \n", getLocalName() );
		System.out
			.println( "--------------------------------------------------------------------------------------------------" );

		// Configura o arquivo de propriedades
		PropertiesHandler.setResourceBundle( "config" );

		try
		{
			Thread.sleep( Integer.parseInt( PropertiesHandler.getString( "CONFIG.WAIT_START" ) ) );
		}
		catch ( InterruptedException e )
		{
			e.printStackTrace();
		}
		int period = Integer.parseInt( PropertiesHandler.getString( "MONTADORA.ESTOQUE_MINIMO.CELTA" ) );
		addBehaviour( new TickerBehaviour( this, ( period * 1000 ) )
		{

			/**
			 * <p>
			 * Field <code>serialVersionUID</code>
			 * </p>
			 */
			private static final long serialVersionUID = 1L;

			@Override
			protected void onTick()
			{
				boolean isRandom = Boolean.parseBoolean( PropertiesHandler
					.getString( "CONSUMIDOR.SIMULACAO_COMPRA.ALEATORIA" ) );
				if ( isRandom )
				{

					int compraAleatoria = ( int ) ( 1 + Math.random()
						* Integer.parseInt( PropertiesHandler.getString( "CONSUMIDOR.SIMULACAO_COMPRA.MAXIMO" ) ) );
					compraRealizada( Produto.PRODUTO_CARRO_CELTA, compraAleatoria );
				}
				else
				{
					compraRealizada( Produto.PRODUTO_CARRO_CELTA, Integer.parseInt( PropertiesHandler
						.getString( "CONSUMIDOR.SIMULACAO_COMPRA.FIXA" ) ) );
				}
			}
		} );
	}

	/**
	 * <p>
	 * </p>
	 * 
	 * @see jade.core.Agent#takeDown()
	 */
	@Override
	protected void takeDown()
	{
		System.err.println( "---------------------------------------" );
		System.err.printf( "Agente %s FINALIZADO\n", getLocalName() );
		System.err.println( "---------------------------------------" );
	}

	/**
	 * <p>
	 * </p>
	 * 
	 * @param produto
	 * @param qtde
	 */
	public void verificaEstoque( final String produto, final int qtde )
	{
		int qtdeEstoque = this.estoque.get( produto );
		System.out.printf( "%s ESTOQUE - Produto %s, Quantidade %d\n", getLocalName(), produto, qtdeEstoque );
		// Se existir qtde suficiente em estoque dá baixa, caso contário faz um pedido com a qtde
		// que falta
		if ( qtde <= qtdeEstoque )
		{
			baixaEstoque( produto, qtde );
		}
		else
		{
			addBehaviour( new IniciaCFP( produto, ( qtde - qtdeEstoque ) ) );
		}
	}

	/**
	 * <p>
	 * Field <code>estoque</code>
	 * </p>
	 */
	protected Map<String, Integer> estoque = new HashMap<String, Integer>();

}
